package com.kamistoat.meimeimall.meimeimallproduct.service.impl;


import com.kamistoat.meimeimall.common.utils.PageUtils;
import com.kamistoat.meimeimall.common.utils.Query;
import com.kamistoat.meimeimall.common.annotation.WithCache;
import com.kamistoat.meimeimall.common.constant.ProductConstant;
import com.kamistoat.meimeimall.meimeimallproduct.service.CategoryBrandRelationService;
import com.kamistoat.meimeimall.meimeimallproduct.vo.thymeleaf.Catalog2Vo;
import com.kamistoat.meimeimall.meimeimallproduct.vo.thymeleaf.Catalog3Vo;
import com.kamistoat.meimeimall.meimeimallproduct.dao.CategoryDao;
import com.kamistoat.meimeimall.meimeimallproduct.entity.CategoryEntity;
import org.redisson.api.RLock;
import org.redisson.api.RReadWriteLock;
import org.redisson.api.RedissonClient;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.cache.annotation.CacheEvict;
import org.springframework.cache.annotation.Caching;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.stereotype.Service;

import java.util.*;
import java.util.stream.Collectors;

import com.baomidou.mybatisplus.core.conditions.query.QueryWrapper;
import com.baomidou.mybatisplus.core.metadata.IPage;
import com.baomidou.mybatisplus.extension.service.impl.ServiceImpl;

import com.kamistoat.meimeimall.meimeimallproduct.service.CategoryService;
import org.springframework.transaction.annotation.Transactional;
import org.springframework.util.StringUtils;


@Service("categoryService")
public class CategoryServiceImpl extends ServiceImpl<CategoryDao, CategoryEntity> implements CategoryService {

    @Autowired
    CategoryBrandRelationService categoryBrandRelationService;

    // 自动注入redis的template，用于使用redis的库构造类
    @Autowired
    StringRedisTemplate stringRedisTemplate;

    // 自动注入Redisson客户端类
    @Autowired
    RedissonClient redissonClient;

    @Override
    public PageUtils queryPage(Map<String, Object> params) {
        IPage<CategoryEntity> page = this.page(
                new Query<CategoryEntity>().getPage(params),
                new QueryWrapper<CategoryEntity>()
        );

        return new PageUtils(page);
    }

    /**
     * 带缓查询所有商品分类，构造成树形结构后返回。
     */
    @Override
    @WithCache(prefix = ProductConstant.HOME_PAGE_TREE_CATEGORY_REDIS_PREFIX, isList = true, returnType = CategoryEntity.class)
    public List<CategoryEntity> listWithTree() {
        // 由于类继承了ServiceImpl<CategoryDao, CategoryEntity>，
        // 所以baseMapper就是CategoryDao
        // 当然如果不嫌麻烦，也可以在类头自动注入CategoryDao来使用
        // 查询条件null代表查询所有数据
        // Dao.selectList()可以按照条件进行批量查询，查询结果将会自动封装成List形式
        List<CategoryEntity> categoryEntityList = baseMapper.selectList(null);
        if (categoryEntityList.size() == 0) {
            return Collections.emptyList();
        } else {
            // 组装树形的父子结构
            // 首先找出所有的一级分类————其parent_cid为0，也就是没有父id
            // 因此使用.stream().filter(函数体)对所有分类进行筛选，找到ParentCid==0
            // stream编程中，.filter(()->{})可以看做是一个插件，它的作用就是对.前的list进行过滤
            // .filter()插件的存在不影响.map()的位置，.map()可以在.filter()的前面或后面，但是.filter()总是对它前面的list进行过滤
            List<CategoryEntity> level1Menus = categoryEntityList.stream().filter((categoryEntity) -> {
                // .filter()前是原始的list
                // .filter()的return值是true/false，如果返回true，则保留这一条数据，如果为false则删除这一条数据。
                return categoryEntity.getParentCid() == 0;
            }).map((menu) -> {
                // .map()跟在.filter()之后，此时经过.filter()得到的list中只有一级菜单分类，.map()是在这个一级菜单list上进行操作
                // 对这个list中的每一个一级菜单，继续寻找每个一级分类的子分类
                // 寻找子分类是一个递归方法，它会从当前这个第一级分类开始，一直向下找到每一级分类的子分类
                menu.setChildren(getChildren(menu, categoryEntityList));
                return menu;
            }).sorted((menu1, menu2) -> {
                // 此时经过.map()后得到的是某一级上的全部分类(实体类中的children保存下一级分类)。再将他们按照其自身的Sort值来进行排序
                // 由于在有些数据的sort列没有填数据，也就是null，不加处理会出错
                return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
            }).collect(Collectors.toList());

            return level1Menus;
        }
    }


    /**
     * 一个递归方法，用于从全体分类中寻找当前分类的子分类
     *
     * @param categoryEntity     当前的某一级分类
     * @param categoryEntityList 全部分类原始list
     * @return 当前某一级分类的子分类
     */
    public List<CategoryEntity> getChildren(CategoryEntity categoryEntity, List<CategoryEntity> categoryEntityList) {
        // 同样使用.stream().filter(函数体)对所有分类进行筛选，找到ParentCid==当前分类的id
        List<CategoryEntity> childrenMenus = categoryEntityList.stream().filter((categoryEntityF) -> {
            return categoryEntityF.getParentCid().equals(categoryEntity.getCatId());
            // 第一次执行到这里，找出了第一级分类的所有子分类
            // 之后递归每次执行到这里，都是在向下寻找子分类
        }).map((menu) -> {
            // 遍历刚刚找到的子分类，然后这里就是执行递归了
            // 将自身作为getChildren的输入变量，再去找自身的子分类
            menu.setChildren(getChildren(menu, categoryEntityList));
            return menu;
        }).sorted((menu1, menu2) -> {
            return (menu1.getSort() == null ? 0 : menu1.getSort()) - (menu2.getSort() == null ? 0 : menu2.getSort());
        }).collect(Collectors.toList());

        return childrenMenus;
    }

    /**
     * 我们自己编写的方法
     * 在一定的逻辑条件下，对菜单进行删除
     *
     * @param asList 传入的菜单id列表
     */
    @Override
    public void removeMenuByIds(List<Long> asList) {
        // 暂时我们还没有写检查是否允许删除的代码
        // TODO  首先检查当前要删除的菜单，是否被别的地方引用

        // 直接调用baseMapper的deleteBatchIds就可以批量删除
        // 但这是物理删除，真删了，以后也找不回来了。我们建议使用逻辑删除。
        // 只需要开启mybatis的逻辑删除功能，依然调用deleteBatchIds，但就会变成是逻辑删除
        baseMapper.deleteBatchIds(asList);
    }

    /**
     * 我们自己编写的方法
     * 根据传入的菜单id，寻找其全部父菜单id，制作成一个long[]返回
     *
     * @param catelogId 某个底层菜单Id
     * @return long[]全部父菜单Id+自身Id
     */
    @Override
    public Long[] findCatelogPath(Long catelogId) {
        // 查菜单路径
        List<Long> catelogPathList = new ArrayList<Long>();
        CategoryEntity categoryEntity = this.getById(catelogId);
        while (categoryEntity != null) {
            // 只要非空，一定就是还有父菜单，就一直往上找父菜单Id
            catelogPathList.add(categoryEntity.getCatId());
            categoryEntity = this.getById(categoryEntity.getParentCid());
        }
        Collections.reverse(catelogPathList);
        Long[] catelogPath = catelogPathList.toArray(new Long[0]);
        return catelogPath;
    }

    /**
     * 我们自己编写的方法
     * 一并更新所有冗余存储的数据表
     * <p>
     * 使用SpringCache来实现缓存层
     * 方法updateDetailWithRedisson()手动实现reids，且使用Redisson为程序加写锁
     * <p>
     * 使用@CacheEvict注解开启缓存失效模式保证一致性，记得指定缓存空间和键名
     *
     * @param category 传来的Entity
     */
    @Caching(evict = {
            @CacheEvict(value = {"category"}, key = "'TopCategorys'"),
            @CacheEvict(value = "category", key = "'Sec&ThrCategorys'")
    })
    @Override
    @Transactional
    public void updateDetail(CategoryEntity category) {
        // 先把自身数据表给更新了
        this.updateById(category);
        if (!StringUtils.isEmpty(category.getName())) {
            // 调用冗余字段所在的service，对冗余字段进行更新
            categoryBrandRelationService.updateCBRelation_Category(category.getCatId(), category.getName());
        }
        // TODO  如果有其他引用了category字段的地方，还需要更新其他冗余字段
    }

    /**
     * 我们自己编写的方法
     * 一并更新所有冗余存储的数据表
     * <p>
     * 手动实现reids，且使用Redisson为程序加写锁
     *
     * @param category 传来的Entity
     */
    @Transactional
    public void updateDetailWithRedisson(CategoryEntity category) {
        // 获得一个读写锁来锁住修改
        RReadWriteLock readWriteLock = redissonClient.getReadWriteLock("product-CatalogJson-Lock");
        RLock rLock = readWriteLock.writeLock();
        rLock.lock();
        // 加了写锁后才允许对mysql进行修改
        try {
            // 先把自身数据表给更新了
            this.updateById(category);
            if (!StringUtils.isEmpty(category.getName())) {
                // 调用冗余字段所在的service，对冗余字段进行更新
                categoryBrandRelationService.updateCBRelation_Category(category.getCatId(), category.getName());
            }
            // TODO  如果有其他引用了category字段的地方，还需要更新其他冗余字段
        } finally {
            // 无论修改是否成功，都应该删除掉缓存中的数据。注意键名不要写错了
            stringRedisTemplate.delete("catalogJSON");
            // 同时还要释放写锁
            rLock.unlock();
        }
    }

    /**
     * 带缓查询所有一级菜单
     *
     * @return 列表
     */
    @Override
    @WithCache(prefix = ProductConstant.HOME_PAGE_TOP_CATEGORY_REDIS_PREFIX, isList = true, returnType = CategoryEntity.class)
    public List<CategoryEntity> getTopCategorys() {
        return this.list(new QueryWrapper<CategoryEntity>().eq("cat_level", 1));
    }


    /**
     * 带缓查询所有二级和三级分类
     *
     * @return
     */
    @WithCache(prefix = ProductConstant.HOME_PAGE_SECOND_CATEGORY_REDIS_PREFIX, isList = false, returnType = Map.class)
    @Override
    public Map<String, List<Catalog2Vo>> getSecondCategorys() {
        List<CategoryEntity> categoryEntityList = baseMapper.selectList(null);
        Map<String, List<Catalog2Vo>> catalogJson = new HashMap<>();
        List<CategoryEntity> topCategoryList = getByParentId(categoryEntityList, 0L);
        for (CategoryEntity category1Entity : topCategoryList) {
            List<CategoryEntity> catalog2List =
                    getByParentId(categoryEntityList, category1Entity.getCatId());
            if (catalog2List != null && catalog2List.size() > 0) {
                List<Catalog2Vo> catalog2VoList = new ArrayList<>();
                for (CategoryEntity category2Entity : catalog2List) {
                    Catalog2Vo catalog2Vo = new Catalog2Vo();
                    catalog2Vo.setCatalog1Id(category1Entity.getCatId().toString());
                    catalog2Vo.setId(category2Entity.getCatId().toString());
                    catalog2Vo.setName(category2Entity.getName());
                    List<Catalog3Vo> catalog3VoList = new ArrayList<>(Collections.emptyList());
                    List<CategoryEntity> catalog3List =
                            getByParentId(categoryEntityList, category2Entity.getCatId());
                    if (catalog3List != null && catalog3List.size() > 0) {
                        for (CategoryEntity category3Entity : catalog3List) {
                            Catalog3Vo catalog3Vo = new Catalog3Vo();
                            catalog3Vo.setCatalog2Id(category2Entity.getCatId().toString());
                            catalog3Vo.setId(category3Entity.getCatId().toString());
                            catalog3Vo.setName(category3Entity.getName());
                            catalog3VoList.add(catalog3Vo);
                        }
                    }
                    catalog2Vo.setCatalog3List(catalog3VoList);
                    catalog2VoList.add(catalog2Vo);
                }
                catalogJson.put(category1Entity.getCatId().toString(), catalog2VoList);
            }
        }
        return catalogJson;
    }

    /**
     * 获取指定 categoryId 的级别
     *
     * @param categoryId
     */
    @Override
    @WithCache(prefix = ProductConstant.CATEGORY_LEVEL_REDIS_PREFIX + "#categoryId", isList = false, returnType = Integer.class)
    public int getCategoryLevel(Long categoryId) {
        CategoryEntity one = this.getOne(new QueryWrapper<CategoryEntity>().select("cat_level").eq("cat_id", categoryId));
        return one.getCatLevel();
    }


    /**
     * 用于优化上面的那个方法，把所有的查询改成对已经查出来的所有的菜单进行筛选
     *
     * @param categoryEntityList 已经查出来的所有菜单集合
     * @param parent_cid         用于筛选的父菜单id
     * @return 筛选结果
     */
    private List<CategoryEntity> getByParentId(List<CategoryEntity> categoryEntityList, Long parent_cid) {
        List<CategoryEntity> collect = categoryEntityList.stream().filter(categoryEntity -> {
            return categoryEntity.getParentCid().equals(parent_cid);
        }).collect(Collectors.toList());
        return collect;
    }

}